﻿namespace GMap.NET.MapProviders
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.Net;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Xml;
    using GMap.NET.Internals;
    using GMap.NET.Projections;

    public abstract class BingMapProviderBase : GMapProvider, RoutingProvider, GeocodingProvider
    {
        public BingMapProviderBase()
        {
            MaxZoom = null;
            RefererUrl = "http://www.bing.com/maps/";
            Copyright = string.Format("©{0} Microsoft Corporation, ©{0} NAVTEQ, ©{0} Image courtesy of NASA", DateTime.Today.Year);
        }

        public string Version = "4810";

        /// <summary>
        /// Bing Maps Customer Identification.
        /// |
        /// FOR LEGAL AND COMMERCIAL USAGE SET YOUR OWN REGISTERED KEY
        /// |
        /// http://msdn.microsoft.com/en-us/library/ff428642.aspx
        /// </summary>
        public string ClientKey = string.Empty;

        internal string SessionId = string.Empty;

        /// <summary>
        /// set true to append SessionId on requesting tiles
        /// </summary>
        public bool ForceSessionIdOnTileAccess = false;

        /// <summary>
        /// set true to avoid using dynamic tile url format
        /// </summary>
        public bool DisableDynamicTileUrlFormat = false;

        /// <summary>
        /// Converts tile XY coordinates into a QuadKey at a specified level of detail.
        /// </summary>
        /// <param name="tileX">Tile X coordinate.</param>
        /// <param name="tileY">Tile Y coordinate.</param>
        /// <param name="levelOfDetail">Level of detail, from 1 (lowest detail)
        /// to 23 (highest detail).</param>
        /// <returns>A string containing the QuadKey.</returns>       
        internal string TileXYToQuadKey(long tileX, long tileY, int levelOfDetail)
        {
            StringBuilder quadKey = new StringBuilder();
            for (int i = levelOfDetail; i > 0; i--)
            {
                char digit = '0';
                int mask = 1 << (i - 1);
                if ((tileX & mask) != 0)
                {
                    digit++;
                }
                if ((tileY & mask) != 0)
                {
                    digit++;
                    digit++;
                }
                quadKey.Append(digit);
            }
            return quadKey.ToString();
        }

        /// <summary>
        /// Converts a QuadKey into tile XY coordinates.
        /// </summary>
        /// <param name="quadKey">QuadKey of the tile.</param>
        /// <param name="tileX">Output parameter receiving the tile X coordinate.</param>
        /// <param name="tileY">Output parameter receiving the tile Y coordinate.</param>
        /// <param name="levelOfDetail">Output parameter receiving the level of detail.</param>
        internal void QuadKeyToTileXY(string quadKey, out int tileX, out int tileY, out int levelOfDetail)
        {
            tileX = tileY = 0;
            levelOfDetail = quadKey.Length;
            for (int i = levelOfDetail; i > 0; i--)
            {
                int mask = 1 << (i - 1);
                switch (quadKey[levelOfDetail - i])
                {
                    case '0':
                        break;

                    case '1':
                        tileX |= mask;
                        break;

                    case '2':
                        tileY |= mask;
                        break;

                    case '3':
                        tileX |= mask;
                        tileY |= mask;
                        break;

                    default:
                        throw new ArgumentException("Invalid QuadKey digit sequence.");
                }
            }
        }

        #region GMapProvider Members
        public override Guid Id
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override string Name
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public override PureProjection Projection
        {
            get
            {
                return MercatorProjection.Instance;
            }
        }

        GMapProvider[] overlays;
        public override GMapProvider[] Overlays
        {
            get
            {
                if (overlays == null)
                {
                    overlays = new GMapProvider[] { this };
                }
                return overlays;
            }
        }

        public override PureImage GetTileImage(GPoint pos, int zoom)
        {
            throw new NotImplementedException();
        }
        #endregion

        public bool TryCorrectVersion = true;

        /// <summary>
        /// set false to use your own key. 
        /// FOR LEGAL AND COMMERCIAL USAGE SET YOUR OWN REGISTERED KEY
        /// http://msdn.microsoft.com/en-us/library/ff428642.aspx
        /// </summary>
        public bool TryGetDefaultKey = true;
        static bool init = false;

        public override void OnInitialized()
        {
            if (!init)
            {
                try
                {
                    var key = ClientKey;

                    // to avoid registration stuff, default key
                    if (TryGetDefaultKey && string.IsNullOrEmpty(ClientKey))
                    {
                        //old: Vx8dmDflxzT02jJUG8bEjMU07Xr9QWRpPTeRuAZTC1uZFQdDCvK/jUbHKdyHEWj4LvccTPoKofDHtzHsWu/0xuo5u2Y9rj88
                        key = Stuff.GString("Jq7FrGTyaYqcrvv9ugBKv4OVSKnmzpigqZtdvtcDdgZexmOZ2RugOexFSmVzTAhOWiHrdhFoNCoySnNF3MyyIOo5u2Y9rj88");
                    }

                    #region -- try get sesion key --
                    if (!string.IsNullOrEmpty(key))
                    {
                        string keyResponse = GMaps.Instance.UseUrlCache ? Cache.Instance.GetContent("BingLoggingServiceV1" + key, CacheType.UrlCache, TimeSpan.FromHours(8)) : string.Empty;

                        if (string.IsNullOrEmpty(keyResponse))
                        {
                            // Bing Maps WPF Control
                            // http://dev.virtualearth.net/webservices/v1/LoggingService/LoggingService.svc/Log?entry=0&auth={0}&fmt=1&type=3&group=MapControl&name=WPF&version=1.0.0.0&session=00000000-0000-0000-0000-000000000000&mkt=en-US

                            keyResponse = GetContentUsingHttp(string.Format("http://dev.virtualearth.net/webservices/v1/LoggingService/LoggingService.svc/Log?entry=0&fmt=1&type=3&group=MapControl&name=AJAX&mkt=en-us&auth={0}&jsonp=microsoftMapsNetworkCallback", key));

                            if (!string.IsNullOrEmpty(keyResponse) && keyResponse.Contains("ValidCredentials"))
                            {
                                if (GMaps.Instance.UseUrlCache)
                                {
                                    Cache.Instance.SaveContent("BingLoggingServiceV1" + key, CacheType.UrlCache, keyResponse);
                                }
                            }
                        }

                        if (!string.IsNullOrEmpty(keyResponse) && keyResponse.Contains("sessionId") && keyResponse.Contains("ValidCredentials"))
                        {
                            // microsoftMapsNetworkCallback({"sessionId" : "xxx", "authenticationResultCode" : "ValidCredentials"})

                            SessionId = keyResponse.Split(',')[0].Split(':')[1].Replace("\"", string.Empty).Replace(" ", string.Empty);
                            Debug.WriteLine("GMapProviders.BingMap.SessionId: " + SessionId);
                        }
                        else
                        {
                            Debug.WriteLine("BingLoggingServiceV1: " + keyResponse);
                        }
                    }
                    #endregion

                    // supporting old road
                    if (TryCorrectVersion && DisableDynamicTileUrlFormat)
                    {
                        #region -- get the version --
                        string url = @"http://www.bing.com/maps";
                        string html = GMaps.Instance.UseUrlCache ? Cache.Instance.GetContent(url, CacheType.UrlCache, TimeSpan.FromDays(7)) : string.Empty;

                        if (string.IsNullOrEmpty(html))
                        {
                            html = GetContentUsingHttp(url);
                            if (!string.IsNullOrEmpty(html))
                            {
                                if (GMaps.Instance.UseUrlCache)
                                {
                                    Cache.Instance.SaveContent(url, CacheType.UrlCache, html);
                                }
                            }
                        }

                        if (!string.IsNullOrEmpty(html))
                        {
                            #region -- match versions --

                            Regex reg = new Regex("tilegeneration:(\\d*)", RegexOptions.IgnoreCase);
                            Match mat = reg.Match(html);
                            if (mat.Success)
                            {
                                GroupCollection gc = mat.Groups;
                                int count = gc.Count;
                                if (count == 2)
                                {
                                    string ver = gc[1].Value;
                                    string old = GMapProviders.BingMap.Version;
                                    if (ver != old)
                                    {
                                        GMapProviders.BingMap.Version = ver;
                                        GMapProviders.BingSatelliteMap.Version = ver;
                                        GMapProviders.BingHybridMap.Version = ver;
#if DEBUG
                              Debug.WriteLine("GMapProviders.BingMap.Version: " + ver + ", old: " + old + ", consider updating source");
                              if(Debugger.IsAttached)
                              {
                                 Thread.Sleep(5555);
                              }
#endif
                                    }
                                    else
                                    {
                                        Debug.WriteLine("GMapProviders.BingMap.Version: " + ver + ", OK");
                                    }
                                }
                            }
                            #endregion
                        }
                        #endregion
                    }

                    init = true; // try it only once
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("TryCorrectBingVersions failed: " + ex);
                }
            }
        }

        protected override bool CheckTileImageHttpResponse(WebResponse response)
        {
            var pass = base.CheckTileImageHttpResponse(response);
            if (pass)
            {
                var tileInfo = response.Headers.Get("X-VE-Tile-Info");
                if (tileInfo != null)
                {
                    return !tileInfo.Equals("no-tile");
                }
            }
            return pass;
        }

        internal string GetTileUrl(string imageryType)
        {
            //Retrieve map tile URL from the Imagery Metadata service: http://msdn.microsoft.com/en-us/library/ff701716.aspx
            //This ensures that the current tile URL is always used. 
            //This will prevent the app from breaking when the map tiles change.

            string ret = string.Empty;
            if (!string.IsNullOrEmpty(SessionId))
            {
                try
                {
                    string url = "http://dev.virtualearth.net/REST/V1/Imagery/Metadata/" + imageryType + "?output=xml&key=" + SessionId;

                    string r = GMaps.Instance.UseUrlCache ? Cache.Instance.GetContent("GetTileUrl" + imageryType, CacheType.UrlCache, TimeSpan.FromDays(7)) : string.Empty;
                    bool cache = false;

                    if (string.IsNullOrEmpty(r))
                    {
                        r = GetContentUsingHttp(url);
                        cache = true;
                    }

                    if (!string.IsNullOrEmpty(r))
                    {
                        XmlDocument doc = new XmlDocument();
                        doc.LoadXml(r);

                        XmlNode xn = doc["Response"];
                        string statuscode = xn["StatusCode"].InnerText;
                        if (string.Compare(statuscode, "200", true) == 0)
                        {
                            xn = xn["ResourceSets"]["ResourceSet"]["Resources"];
                            XmlNodeList xnl = xn.ChildNodes;
                            foreach (XmlNode xno in xnl)
                            {
                                XmlNode imageUrl = xno["ImageUrl"];

                                if (imageUrl != null && !string.IsNullOrEmpty(imageUrl.InnerText))
                                {
                                    if (cache && GMaps.Instance.UseUrlCache)
                                    {
                                        Cache.Instance.SaveContent("GetTileUrl" + imageryType, CacheType.UrlCache, r);
                                    }

                                    var baseTileUrl = imageUrl.InnerText;

                                    if (baseTileUrl.Contains("{key}") || baseTileUrl.Contains("{token}"))
                                    {
                                        baseTileUrl.Replace("{key}", SessionId).Replace("{token}", SessionId);
                                    }
                                    else if (ForceSessionIdOnTileAccess)
                                    {
                                        // haven't seen anyone doing that, yet? ;/                            
                                        baseTileUrl += "&key=" + SessionId;
                                    }

                                    Debug.WriteLine("GetTileUrl, UrlFormat[" + imageryType + "]: " + baseTileUrl);

                                    ret = baseTileUrl;
                                    break;
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("GetTileUrl: Error getting Bing Maps tile URL - " + ex);
                }
            }
            return ret;
        }

        #region RoutingProvider

        public MapRoute GetRoute(PointLatLng start, PointLatLng end, bool avoidHighways, bool walkingMode, int Zoom)
        {
            string tooltip;
            int numLevels;
            int zoomFactor;
            MapRoute ret = null;
            List<PointLatLng> points = GetRoutePoints(MakeRouteUrl(start, end, LanguageStr, avoidHighways, walkingMode), Zoom, out tooltip, out numLevels, out zoomFactor);
            if (points != null)
            {
                ret = new MapRoute(points, tooltip);
            }
            return ret;
        }

        public MapRoute GetRoute(string start, string end, bool avoidHighways, bool walkingMode, int Zoom)
        {
            string tooltip;
            int numLevels;
            int zoomFactor;
            MapRoute ret = null;
            List<PointLatLng> points = GetRoutePoints(MakeRouteUrl(start, end, LanguageStr, avoidHighways, walkingMode), Zoom, out tooltip, out numLevels, out zoomFactor);
            if (points != null)
            {
                ret = new MapRoute(points, tooltip);
            }
            return ret;
        }

        string MakeRouteUrl(string start, string end, string language, bool avoidHighways, bool walkingMode)
        {
            string addition = avoidHighways ? "&avoid=highways" : string.Empty;
            string mode = walkingMode ? "Walking" : "Driving";

            return string.Format(CultureInfo.InvariantCulture, RouteUrlFormatPointQueries, mode, start, end, addition, SessionId);
        }

        string MakeRouteUrl(PointLatLng start, PointLatLng end, string language, bool avoidHighways, bool walkingMode)
        {
            string addition = avoidHighways ? "&avoid=highways" : string.Empty;
            string mode = walkingMode ? "Walking" : "Driving";

            return string.Format(CultureInfo.InvariantCulture, RouteUrlFormatPointLatLng, mode, start.Lat, start.Lng, end.Lat, end.Lng, addition, SessionId);
        }

        List<PointLatLng> GetRoutePoints(string url, int zoom, out string tooltipHtml, out int numLevel, out int zoomFactor)
        {
            List<PointLatLng> points = null;
            tooltipHtml = string.Empty;
            numLevel = -1;
            zoomFactor = -1;
            try
            {
                string route = GMaps.Instance.UseRouteCache ? Cache.Instance.GetContent(url, CacheType.RouteCache) : string.Empty;

                if (string.IsNullOrEmpty(route))
                {
                    route = GetContentUsingHttp(url);

                    if (!string.IsNullOrEmpty(route))
                    {
                        if (GMaps.Instance.UseRouteCache)
                        {
                            Cache.Instance.SaveContent(url, CacheType.RouteCache, route);
                        }
                    }
                }

                // parse values
                if (!string.IsNullOrEmpty(route))
                {
                    #region -- title --
                    int tooltipEnd = 0;
                    {
                        int x = route.IndexOf("<RoutePath><Line>") + 17;
                        if (x >= 17)
                        {
                            tooltipEnd = route.IndexOf("</Line></RoutePath>", x + 1);
                            if (tooltipEnd > 0)
                            {
                                int l = tooltipEnd - x;
                                if (l > 0)
                                {
                                    //tooltipHtml = route.Substring(x, l).Replace(@"\x26#160;", " ");
                                    tooltipHtml = route.Substring(x, l);
                                }
                            }
                        }
                    }
                    #endregion

                    #region -- points --
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(route);
                    XmlNode xn = doc["Response"];
                    string statuscode = xn["StatusCode"].InnerText;
                    switch (statuscode)
                    {
                        case "200":
                            {
                                xn = xn["ResourceSets"]["ResourceSet"]["Resources"]["Route"]["RoutePath"]["Line"];
                                XmlNodeList xnl = xn.ChildNodes;
                                if (xnl.Count > 0)
                                {
                                    points = new List<PointLatLng>();
                                    foreach (XmlNode xno in xnl)
                                    {
                                        XmlNode latitude = xno["Latitude"];
                                        XmlNode longitude = xno["Longitude"];
                                        points.Add(new PointLatLng(double.Parse(latitude.InnerText, CultureInfo.InvariantCulture),
                                                                   double.Parse(longitude.InnerText, CultureInfo.InvariantCulture)));
                                    }
                                }
                                break;
                            }
                        // no status implementation on routes yet although when introduced these are the codes. Exception will be catched.
                        case "400":
                            throw new Exception("Bad Request, The request contained an error.");
                        case "401":
                            throw new Exception("Unauthorized, Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation.");
                        case "403":
                            throw new Exception("Forbidden, The request is for something forbidden. Authorization will not help.");
                        case "404":
                            throw new Exception("Not Found, The requested resource was not found.");
                        case "500":
                            throw new Exception("Internal Server Error, Your request could not be completed because there was a problem with the service.");
                        case "501":
                            throw new Exception("Service Unavailable, There's a problem with the service right now. Please try again later.");
                        default:
                            points = null;
                            break; // unknown, for possible future error codes
                    }
                    #endregion
                }
            }
            catch (Exception ex)
            {
                points = null;
                Debug.WriteLine("GetRoutePoints: " + ex);
            }
            return points;
        }

        // example : http://dev.virtualearth.net/REST/V1/Routes/Driving?o=xml&wp.0=44.979035,-93.26493&wp.1=44.943828508257866,-93.09332862496376&optmz=distance&rpo=Points&key=[PROVIDEYOUROWNKEY!!]
        static readonly string RouteUrlFormatPointLatLng = "http://dev.virtualearth.net/REST/V1/Routes/{0}?o=xml&wp.0={1},{2}&wp.1={3},{4}{5}&optmz=distance&rpo=Points&key={6}";
        static readonly string RouteUrlFormatPointQueries = "http://dev.virtualearth.net/REST/V1/Routes/{0}?o=xml&wp.0={1}&wp.1={2}{3}&optmz=distance&rpo=Points&key={4}";

        #endregion RoutingProvider

        #region GeocodingProvider

        public GeoCoderStatusCode GetPoints(string keywords, out List<PointLatLng> pointList)
        {
            //Escape keywords to better handle special characters.
            return GetLatLngFromGeocoderUrl(MakeGeocoderUrl("q=" + Uri.EscapeDataString(keywords)), out pointList);
        }

        public PointLatLng? GetPoint(string keywords, out GeoCoderStatusCode status)
        {
            List<PointLatLng> pointList;
            status = GetPoints(keywords, out pointList);
            return pointList != null && pointList.Count > 0 ? pointList[0] : (PointLatLng?)null;
        }

        public GeoCoderStatusCode GetPoints(Placemark placemark, out List<PointLatLng> pointList)
        {
            return GetLatLngFromGeocoderUrl(MakeGeocoderDetailedUrl(placemark), out pointList);
        }

        public PointLatLng? GetPoint(Placemark placemark, out GeoCoderStatusCode status)
        {
            List<PointLatLng> pointList;
            status = GetLatLngFromGeocoderUrl(MakeGeocoderDetailedUrl(placemark), out pointList);
            return pointList != null && pointList.Count > 0 ? pointList[0] : (PointLatLng?)null;
        }

        string MakeGeocoderDetailedUrl(Placemark placemark)
        {
            string parameters = string.Empty;

            if (!AddFieldIfNotEmpty(ref parameters, "countryRegion", placemark.CountryNameCode))
                AddFieldIfNotEmpty(ref parameters, "countryRegion", placemark.CountryName);

            AddFieldIfNotEmpty(ref parameters, "adminDistrict", placemark.DistrictName);
            AddFieldIfNotEmpty(ref parameters, "locality", placemark.LocalityName);
            AddFieldIfNotEmpty(ref parameters, "postalCode", placemark.PostalCodeNumber);

            if (!string.IsNullOrEmpty(placemark.HouseNo))
                AddFieldIfNotEmpty(ref parameters, "addressLine", placemark.ThoroughfareName + " " + placemark.HouseNo);
            else
                AddFieldIfNotEmpty(ref parameters, "addressLine", placemark.ThoroughfareName);

            return MakeGeocoderUrl(parameters);
        }

        bool AddFieldIfNotEmpty(ref string Input, string FieldName, string Value)
        {
            if (!string.IsNullOrEmpty(Value))
            {
                if (string.IsNullOrEmpty(Input))
                    Input = string.Empty;
                else
                    Input = Input + "&";

                Input = Input + FieldName + "=" + Value;

                return true;
            }
            return false;
        }

        public GeoCoderStatusCode GetPlacemarks(PointLatLng location, out List<Placemark> placemarkList)
        {
            // http://msdn.microsoft.com/en-us/library/ff701713.aspx
            throw new NotImplementedException();
        }

        public Placemark? GetPlacemark(PointLatLng location, out GeoCoderStatusCode status)
        {
            // http://msdn.microsoft.com/en-us/library/ff701713.aspx
            throw new NotImplementedException();
        }

        string MakeGeocoderUrl(string keywords)
        {
            return string.Format(CultureInfo.InvariantCulture, GeocoderUrlFormat, keywords, SessionId);
        }

        GeoCoderStatusCode GetLatLngFromGeocoderUrl(string url, out List<PointLatLng> pointList)
        {
            var status = GeoCoderStatusCode.Unknow;
            pointList = null;

            try
            {
                string geo = GMaps.Instance.UseGeocoderCache ? Cache.Instance.GetContent(url, CacheType.GeocoderCache) : string.Empty;

                bool cache = false;

                if (string.IsNullOrEmpty(geo))
                {
                    geo = GetContentUsingHttp(url);

                    if (!string.IsNullOrEmpty(geo))
                    {
                        cache = true;
                    }
                }

                status = GeoCoderStatusCode.Unknow;
                if (!string.IsNullOrEmpty(geo))
                {
                    if (geo.StartsWith("<?xml") && geo.Contains("<Response"))
                    {
                        XmlDocument doc = new XmlDocument();
                        doc.LoadXml(geo);
                        XmlNode xn = doc["Response"];
                        string statuscode = xn["StatusCode"].InnerText;
                        switch (statuscode)
                        {
                            case "200":
                                {
                                    pointList = new List<PointLatLng>();
                                    xn = xn["ResourceSets"]["ResourceSet"]["Resources"];
                                    XmlNodeList xnl = xn.ChildNodes;
                                    foreach (XmlNode xno in xnl)
                                    {
                                        XmlNode latitude = xno["Point"]["Latitude"];
                                        XmlNode longitude = xno["Point"]["Longitude"];
                                        pointList.Add(new PointLatLng(Double.Parse(latitude.InnerText, CultureInfo.InvariantCulture),
                                                                      Double.Parse(longitude.InnerText, CultureInfo.InvariantCulture)));
                                    }

                                    if (pointList.Count > 0)
                                    {
                                        status = GeoCoderStatusCode.G_GEO_SUCCESS;
                                        if (cache && GMaps.Instance.UseGeocoderCache)
                                        {
                                            Cache.Instance.SaveContent(url, CacheType.GeocoderCache, geo);
                                        }
                                        break;
                                    }

                                    status = GeoCoderStatusCode.G_GEO_UNKNOWN_ADDRESS;
                                    break;
                                }

                            case "400":
                                status = GeoCoderStatusCode.G_GEO_BAD_REQUEST;
                                break; // bad request, The request contained an error.
                            case "401":
                                status = GeoCoderStatusCode.G_GEO_BAD_KEY;
                                break; // Unauthorized, Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation.
                            case "403":
                                status = GeoCoderStatusCode.G_GEO_BAD_REQUEST;
                                break; // Forbidden, The request is for something forbidden. Authorization will not help.
                            case "404":
                                status = GeoCoderStatusCode.G_GEO_UNKNOWN_ADDRESS;
                                break; // Not Found, The requested resource was not found. 
                            case "500":
                                status = GeoCoderStatusCode.G_GEO_SERVER_ERROR;
                                break; // Internal Server Error, Your request could not be completed because there was a problem with the service.
                            case "501":
                                status = GeoCoderStatusCode.Unknow;
                                break; // Service Unavailable, There's a problem with the service right now. Please try again later.
                            default:
                                status = GeoCoderStatusCode.Unknow;
                                break; // unknown, for possible future error codes
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                status = GeoCoderStatusCode.ExceptionInCode;
                Debug.WriteLine("GetLatLngFromGeocoderUrl: " + ex);
            }

            return status;
        }

        // http://dev.virtualearth.net/REST/v1/Locations/1%20Microsoft%20Way%20Redmond%20WA%2098052?o=xml&key=BingMapsKey
        static readonly string GeocoderUrlFormat = "http://dev.virtualearth.net/REST/v1/Locations?{0}&o=xml&key={1}";

        #endregion GeocodingProvider
    }

    /// <summary>
    /// BingMapProvider provider
    /// </summary>
    public class BingMapProvider : BingMapProviderBase
    {
        public static readonly BingMapProvider Instance;

        BingMapProvider()
        {
        }

        static BingMapProvider()
        {
            Instance = new BingMapProvider();
        }

        #region GMapProvider Members

        readonly Guid id = new Guid("D0CEB371-F10A-4E12-A2C1-DF617D6674A8");
        public override Guid Id
        {
            get
            {
                return id;
            }
        }

        readonly string name = "BingMap";
        public override string Name
        {
            get
            {
                return name;
            }
        }

        public override PureImage GetTileImage(GPoint pos, int zoom)
        {
            string url = MakeTileImageUrl(pos, zoom, LanguageStr);

            return GetTileImageUsingHttp(url);
        }

        public override void OnInitialized()
        {
            base.OnInitialized();

            if (!DisableDynamicTileUrlFormat)
            {
                //UrlFormat[Road]: http://ecn.{subdomain}.tiles.virtualearth.net/tiles/r{quadkey}.jpeg?g=3179&mkt={culture}&shading=hill

                UrlDynamicFormat = GetTileUrl("Road");
                if (!string.IsNullOrEmpty(UrlDynamicFormat))
                {
                    UrlDynamicFormat = UrlDynamicFormat.Replace("{subdomain}", "t{0}").Replace("{quadkey}", "{1}").Replace("{culture}", "{2}");
                }
            }
        }

        #endregion

        string MakeTileImageUrl(GPoint pos, int zoom, string language)
        {
            string key = TileXYToQuadKey(pos.X, pos.Y, zoom);

            if (!DisableDynamicTileUrlFormat && !string.IsNullOrEmpty(UrlDynamicFormat))
            {
                return string.Format(UrlDynamicFormat, GetServerNum(pos, 4), key, language);
            }

            return string.Format(UrlFormat, GetServerNum(pos, 4), key, Version, language, ForceSessionIdOnTileAccess ? "&key=" + SessionId : string.Empty);
        }

        string UrlDynamicFormat = string.Empty;

        // http://ecn.t0.tiles.virtualearth.net/tiles/r120030?g=875&mkt=en-us&lbl=l1&stl=h&shading=hill&n=z

        static readonly string UrlFormat = "http://ecn.t{0}.tiles.virtualearth.net/tiles/r{1}?g={2}&mkt={3}&lbl=l1&stl=h&shading=hill&n=z{4}";
    }
}